// Copyright (c) 2017 Cisco and/or its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// simple-client is an example VPP management application that exercises the
// govpp API on real-world use-cases.
package main

import (
	"flag"
	"fmt"
	"log"
	"strconv"
	"strings"

	"git.fd.io/govpp.git"
	"git.fd.io/govpp.git/adapter/socketclient"
	"git.fd.io/govpp.git/api"
	"git.fd.io/govpp.git/core"
	"github.com/pan2za/qos-client/binapi/classify"
	interfaces "github.com/pan2za/qos-client/binapi/interface"
	"github.com/pan2za/qos-client/binapi/interface_types"
	"github.com/pan2za/qos-client/binapi/policer"
	"github.com/pan2za/qos-client/binapi/vpe"
)

var (
	jsockAddr = flag.String("jsock", socketclient.DefaultSocketName, "Path to VPP binary API socket file")
)

func jclient_connect_vpp(qos_config *QosConfig) string {

	flag.Parse()
	fmt.Println("go connecting vpp...")
	fmt.Println()

	// connect to VPP asynchronously
	conn, connEv, err := govpp.AsyncConnect(*jsockAddr, core.DefaultMaxReconnectAttempts, core.DefaultReconnectInterval)
	if err != nil {
		//log.Fatalln("ERROR:", err)
		log.Println("ERROR:", err)
		return "failed"
	}
	defer conn.Disconnect()
	fmt.Println("connected.")
	// wait for Connected qos_config
	select {
	case e := <-connEv:
		if e.State != core.Connected {
			//log.Fatalln("ERROR: connecting to VPP failed:", e.Error)
			log.Println("ERROR: connecting to VPP failed:", e.Error)
			return "failed"
		}
	}
	fmt.Println("connected done.")
	// check compatibility of used messages
	ch, err := conn.NewAPIChannel()
	if err != nil {
		//log.Fatalln("ERROR: creating channel failed:", err)
		log.Println("ERROR: creating channel failed:", err)
		return "failed"
	}
	defer ch.Close()
	fmt.Println("api channel open.")
	if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil {
		//log.Fatal(err)
		log.Println(err)
		return "failed"
	}
	fmt.Println("vpe compatiblity checking done.")
	if err := ch.CheckCompatiblity(interfaces.AllMessages()...); err != nil {
		//log.Fatal(err)
		log.Println(err)
		return "failed"
	}
	fmt.Println("compatiblity checking done.")
	// process errors encountered during the example
	defer func() {
		if len(errors) > 0 {
			fmt.Printf("finished with %d errors\n", len(errors))
		} else {
			fmt.Println("finished successfully")
		}
	}()

	//getVppVersion(ch)
	interface_dump_at_startup(ch)
	// use request/reply (channel API)
	//getVppVersion(ch)
	result := jsend_message_vpp(ch, qos_config)
	//getSystemTime(ch)
	//idx := createLoopback(ch)
	//interfaceDump(ch)
	//addIPAddress(ch, idx)
	//ipAddressDump(ch, idx)
	//interfaceNotifications(ch, idx)
	return result
}

func jsend_message_vpp(ch api.Channel, qos_config *QosConfig) string {
	var result string
	fmt.Println("recv key: ", string(qos_config.Type), " ,qos_config type: ", qos_config.Type)
	if strings.Contains(string(qos_config.Type), "/vpp/classify_table") {
		if qos_config.IsAdd {
			result = jadd_ct(ch, qos_config)
		} else {
			result = jdel_ct(ch, qos_config)
		}
	} else if strings.Contains(string(qos_config.Type), "/vpp/policer") {
		if qos_config.IsAdd {
			result = jadd_policer(ch, qos_config)
		} else {
			result = jdel_policer(ch, qos_config)
		}
	} else if strings.Contains(string(qos_config.Type), "/vpp/classify_session") {
		result = jadd_del_cs(ch, qos_config)
	} else if strings.Contains(string(qos_config.Type), "/vpp/set_policer_intf") {
		result = japply_unapply_poli(ch, qos_config)
	} else {
	}
	return result
}

func time_str_to_u32(time_str string) uint32 {

	var time_u32 uint32 = 0
	if len(time_str) == 5 {
		index := strings.Index(time_str, ":")
		hour, err := strconv.Atoi(time_str[:index])
		if err != nil {
			logError(err, "policer time hour add failed")
		}
		min, err := strconv.Atoi(time_str[index+1 : len(time_str)])
		if err != nil {
			logError(err, "policer time min add failed")
		}
		time_u32 = uint32(hour*60 + min)
		fmt.Println(hour, min, time_u32)
	} else {
	}

	return time_u32
}

func jadd_policer(ch api.Channel, qos_config *QosConfig) string {

	qos_config.PolicerRule.StartTime = time_str_to_u32(qos_config.StartTime)
	qos_config.PolicerRule.EndTime = time_str_to_u32(qos_config.EndTime)

	req := qos_config.PolicerRule

	reply := &policer.PolicerAddDelReply{}

	if err := ch.SendRequest(api.Message(&req)).ReceiveReply(reply); err != nil {
		logError(err, "policer add failed")
		return "failed"
	}
	fmt.Printf("Event received! %s executed on %q with value %q\n", qos_config.Type, qos_config.Type, qos_config.IsAdd)
	return strconv.Itoa(int(reply.PolicerIndex))
}

func jdel_policer(ch api.Channel, qos_config *QosConfig) string {

	req := &policer.PolicerAddDel{
		IsAdd: qos_config.IsAdd,
		Name:  qos_config.PolicerName,
	}
	reply := &policer.PolicerAddDelReply{}

	if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
		logError(err, "policer del failed")
		return "failed"
	}
	return "ok"
}

func jadd_ct(ch api.Channel, qos_config *QosConfig) string {
	//var mask = []byte("hello world hell")
	var mask []byte
	var match uint32
	var skip uint32
	if strings.Compare(string(qos_config.Mask), "0") == 0 { //l3 ip4 src
		skip = 1
		match = 1
		mask = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00}

	} else if strings.Compare(string(qos_config.Mask), "1") == 0 { //l3 ip4 dst
		skip = 1
		match = 2
		mask = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
			0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
	} else if strings.Compare(string(qos_config.Mask), "2") == 0 { //l3 ip4 src dst
		skip = 1
		match = 2
		mask = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
			0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
	} else if strings.Compare(string(qos_config.Mask), "3") == 0 { //l3 ip4 src l4 src_port
		skip = 1
		match = 2
		mask = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
			0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
	} else if strings.Compare(string(qos_config.Mask), "4") == 0 { //l3 ip4 dst l4 dst_port
		skip = 1
		match = 2
		mask = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
			0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
	} else if strings.Compare(string(qos_config.Mask), "6") == 0 { //l3 ip6 src
		skip = 1
		match = 2
		mask = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
			0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
			0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
	} else if strings.Compare(string(qos_config.Mask), "7") == 0 { //l3 ip6 dst
		skip = 2
		match = 2
		mask = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
			0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
			0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
	} else if strings.Compare(string(qos_config.Mask), "8") == 0 { //l3 ip6 src dst
		skip = 1
		match = 3
		mask = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
			0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
			0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
			0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
			0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
	} else if strings.Compare(string(qos_config.Mask), "9") == 0 { //l3 ip6 src l4 src_port
		skip = 1
		match = 3
		mask = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
			0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
			0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
	} else if strings.Compare(string(qos_config.Mask), "10") == 0 { //l3 ip6 dst l4 dst_port
		skip = 2
		match = 2
		mask = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
			0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
			0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
			0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
	}

	req := &classify.ClassifyAddDelTable{
		IsAdd:          true,
		DelChain:       false,
		TableIndex:     0xffffffff,
		Nbuckets:       2,
		MemorySize:     2 << 20,
		SkipNVectors:   skip,
		MatchNVectors:  match,
		NextTableIndex: 0xffffffff,
		MissNextIndex:  0xffffffff,
		Mask:           mask,
	}
	reply := &classify.ClassifyAddDelTableReply{}

	if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
		logError(err, "classify table add failed")
		return "failed"
	}
	return strconv.Itoa(int(reply.NewTableIndex))
}

func jdel_ct(ch api.Channel, qos_config *QosConfig) string {

	req := &classify.ClassifyAddDelTable{
		IsAdd:      qos_config.IsAdd,
		TableIndex: qos_config.ClassifyTableIndex,
	}
	reply := &classify.ClassifyAddDelTableReply{}

	if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
		logError(err, "classify table del failed")
		return "failed"
	}
	return "ok"
}

func jadd_del_cs(ch api.Channel, qos_config *QosConfig) string {

	req := qos_config.ClassifySession

	reply := &classify.ClassifyAddDelSessionReply{}

	if err := ch.SendRequest(api.Message(&req)).ReceiveReply(reply); err != nil {
		if qos_config.IsAdd {
			logError(err, "classify table add failed")
		} else {
			logError(err, "classify table del failed")
		}
		return "failed"
	}
	return "ok"
}

func japply_unapply_poli(ch api.Channel, qos_config *QosConfig) string {

	ifidx, ok := get_ifidx(qos_config.SwIfName)
	if ok {
		fmt.Printf("ifname: %s => ifidx %d\n", qos_config.SwIfName, ifidx)
	} else {
		fmt.Println("not found such ifidx")
		return "failed"
	}

	qos_config.SetInterface.SwIfIndex = interface_types.InterfaceIndex(ifidx)

	req := qos_config.SetInterface

	reply := &classify.PolicerClassifySetInterfaceReply{}

	if err := ch.SendRequest(api.Message(&req)).ReceiveReply(reply); err != nil {
		if qos_config.SetInterface.IsAdd {
			logError(err, "apply policer to interface failed.")
		} else {
			logError(err, "unapply policer to interface failed.")
		}
		return "failed"
	}
	return "ok"
}
